//*****************************************************************************
// JFab (http://code.google.com/p/jfab)
// Copyright (c) 2011 JFab.org
// Admin jfab@jeffreythompson.net
//
// See the file "LICENSE.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//*****************************************************************************

package org.jfab.core.model;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.javaruntype.type.Type;
import org.jfab.core.analyzer.InterfaceAnalyzer;
import org.jfab.core.collection.CGInterfaceList;
import org.jfab.core.io.CGSpecReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides additional functionality to a code generator specification.
 */
public class CGSpecUtilities
{
    /** Logger. */
    private static final Logger LOGGER = LoggerFactory
            .getLogger(CGSpecUtilities.class);

    /** Code generator specification. */
    private final CGSpec _cgSpec;

    /** Code generator type utilities. */
    private final CGTypeUtilities _cgTypeUtils;

    /** Code generator type to compilation unit map. */
    private Map<CGType, CGCompilationUnit> _typeToCompilationUnit = new HashMap<CGType, CGCompilationUnit>();

    /**
     * Construct this object with the given parameters.
     * 
     * @param cgSpec Code generator specification. (required)
     * @param cgTypeUtils Code generator type utilities. (required)
     */
    public CGSpecUtilities(final CGSpec cgSpec,
            final CGTypeUtilities cgTypeUtils)
    {
        Validate.notNull(cgSpec, "cgSpec is null");
        Validate.notNull(cgTypeUtils, "cgTypeUtils is null");

        _cgSpec = cgSpec;
        _cgTypeUtils = cgTypeUtils;
    }

    /**
     * @return the cgSpec
     */
    public CGSpec getCgSpec()
    {
        return _cgSpec;
    }

    /**
     * @param cgClass Code generator class.
     * 
     * @return the super class, if any.
     */
    public CGClass getSuperClass(final CGClass cgClass)
    {
        CGClass answer = null;

        answer = findClassByType(cgClass.getExtendsType());

        return answer;
    }

    /**
     * @param cgClass Code generator class.
     * @param interfaceAnalyzer Interface analyzer.
     * 
     * @return the super interfaces, if any.
     */
    public CGInterfaceList getSuperInterfaces(final CGClass cgClass,
            final InterfaceAnalyzer interfaceAnalyzer)
    {
        CGInterfaceList answer = null;

        if (cgClass == null)
        {
            answer = new CGInterfaceList();
        }
        else
        {
            answer = getSuperInterfaces(cgClass.getImplementsInterfaces(),
                    interfaceAnalyzer);
        }

        return answer;
    }

    /**
     * @param cgInterface Code generator interface.
     * @param interfaceAnalyzer Interface analyzer.
     * 
     * @return the super interfaces, if any.
     */
    public CGInterfaceList getSuperInterfaces(final CGInterface cgInterface,
            final InterfaceAnalyzer interfaceAnalyzer)
    {
        CGInterfaceList answer = null;

        if (cgInterface == null)
        {
            answer = new CGInterfaceList();
        }
        else
        {
            answer = getSuperInterfaces(cgInterface.getExtendsTypes(),
                    interfaceAnalyzer);
        }

        return answer;
    }

    /**
     * @param cgSpec Code generator specification.
     * @param cgSpecReader Code generator specification reader.
     * 
     * @return the parent code generator specification, if any.
     */
    public CGSpec loadParentSpec(final CGSpec cgSpec,
            final CGSpecReader cgSpecReader)
    {
        CGSpec answer = null;

        if (cgSpec != null)
        {
            final String parentSpecUrl = cgSpec.getParentSpecUrl();

            if (StringUtils.isNotEmpty(parentSpecUrl))
            {
                URL url;
                try
                {
                    url = new URL(parentSpecUrl);
                    final InputStream inputStream = url.openStream();
                    final Reader reader = new InputStreamReader(inputStream);
                    answer = cgSpecReader.read(reader);
                }
                catch (final MalformedURLException e)
                {
                    e.printStackTrace();
                }
                catch (final IOException e)
                {
                    e.printStackTrace();
                }
            }
        }

        return answer;
    }

    /**
     * @param cgType Code generator type.
     * 
     * @return the code generator interface which corresponds to the parameters.
     */
    private CGClass findClassByType(final CGType cgType)
    {
        CGClass answer = null;

        if (cgType != null)
        {
            answer = (CGClass)_typeToCompilationUnit.get(cgType);

            if (answer == null)
            {
                final List<CGClass> classes = _cgSpec.getClasses();
                final int size = classes.size();

                for (int i = 0; (answer == null) && (i < size); i++)
                {
                    final CGClass cgClass = classes.get(i);

                    if (_cgTypeUtils.equals(cgType, cgClass.getJavaType()))
                    {
                        answer = cgClass;
                    }
                    else
                    {
                        final String fullyQualifiedName = cgClass
                                .getPackageName() + "." + cgClass.getName();

                        final String componentName = _cgTypeUtils
                                .toComponentNameString(cgType);

                        if (componentName.equals(fullyQualifiedName))
                        {
                            answer = cgClass;
                        }
                    }
                }

                if (answer != null)
                {
                    _typeToCompilationUnit.put(cgType, answer);
                }
            }
        }

        return answer;
    }

    /**
     * @param cgType Code generator type.
     * 
     * @return the code generator interface which corresponds to the parameters.
     */
    private CGInterface findInterfaceByType(final CGType cgType)
    {
        CGInterface answer = null;

        if (cgType != null)
        {
            answer = (CGInterface)_typeToCompilationUnit.get(cgType);

            if (answer == null)
            {
                final List<CGInterface> interfaces = _cgSpec.getInterfaces();
                final int size = interfaces.size();

                for (int i = 0; (answer == null) && (i < size); i++)
                {
                    final CGInterface cgInterface = interfaces.get(i);

                    if (_cgTypeUtils.equals(cgType, cgInterface.getJavaType()))
                    {
                        answer = cgInterface;
                    }
                    else
                    {
                        final String fullyQualifiedName = cgInterface
                                .getPackageName() + "." + cgInterface.getName();
                        final String componentName = _cgTypeUtils
                                .toComponentNameString(cgType);

                        if (componentName.equals(fullyQualifiedName))
                        {
                            answer = cgInterface;
                        }
                    }
                }

                if (answer != null)
                {
                    _typeToCompilationUnit.put(cgType, answer);
                }
            }
        }

        return answer;
    }

    /**
     * @param cgType Code generator type.
     * @param interfaceAnalyzer Interface analyzer.
     * 
     * @return the code generator interface which corresponds to the parameters.
     */
    private CGInterface findInterfaceByTypeFromClasspath(final CGType cgType,
            final InterfaceAnalyzer interfaceAnalyzer)
    {
        CGInterface answer = null;

        LOGGER.debug("Looking in the classpath for " + cgType);
        final Type<?> type = _cgTypeUtils.getType(cgType);

        if (type != null)
        {
            final Class<?> aClass = type.getRawClass();
            final String javaSource = null;

            answer = interfaceAnalyzer.analyze(aClass, javaSource);
        }

        return answer;
    }

    /**
     * @param cgSpec Code generator specification.
     * @param cgType Code generator type.
     * 
     * @return the code generator interface which corresponds to the parameters.
     */
    private CGInterface findInterfaceByTypeFromParentSpec(final CGSpec cgSpec,
            final CGType cgType)
    {
        CGInterface answer = null;

        LOGGER.debug("Looking in the parent spec for " + cgType);
        final CGSpecReader cgSpecReader = new CGSpecReader();
        final CGSpec parentSpec = loadParentSpec(cgSpec, cgSpecReader);

        if (parentSpec != null)
        {
            final CGSpecUtilities cgSpecUtils = new CGSpecUtilities(parentSpec,
                    _cgTypeUtils);
            answer = cgSpecUtils.findInterfaceByType(cgType);

            if (answer == null)
            {
                answer = findInterfaceByTypeFromParentSpec(parentSpec, cgType);
            }
        }

        return answer;
    }

    /**
     * @param superInterfaces Code generator types.
     * @param interfaceAnalyzer Interface analyzer.
     * 
     * @return the super interfaces, if any.
     */
    private CGInterfaceList getSuperInterfaces(
            final List<CGType> superInterfaces,
            final InterfaceAnalyzer interfaceAnalyzer)
    {
        final CGInterfaceList answer = new CGInterfaceList();

        for (final CGType cgType : superInterfaces)
        {
            CGInterface extendsInterface = findInterfaceByType(cgType);

            if (extendsInterface == null)
            {
                // Look for it in the parent specification.
                extendsInterface = findInterfaceByTypeFromParentSpec(_cgSpec,
                        cgType);
            }

            if (extendsInterface == null)
            {
                // Look for it in the classpath.
                extendsInterface = findInterfaceByTypeFromClasspath(cgType,
                        interfaceAnalyzer);
            }

            if (extendsInterface != null)
            {
                answer.add(extendsInterface);

                final CGInterfaceList interfaces = getSuperInterfaces(
                        extendsInterface, interfaceAnalyzer);

                if ((interfaces != null) && !interfaces.isEmpty())
                {
                    answer.addAll(interfaces);
                }
            }
        }

        return answer;
    }
}
